#include "AApplication.h"
#include "Log.h"
#include <map>
#include <GL/GL.h>
#include <GL/GLU.h>
#include <iostream>

#define _WIN32_WINDOWS 0x0410
#define _WIN32_WINNT 0x0410
#include <windows.h>

AApplication	*pApp=0;

//typedef std::map<HWND, AApplication*> WNDCALLBACKS;	//Link between window and his AApllication
//typedef std::pair<HWND, AApplication *> WNDCALLBACKPAIR;
//typedef WNDCALLBACKS::iterator		WNDCALLBACK_ITER;

//WNDCALLBACKS	g_Callbacks;

const char *g_cClassName = "PanzersFightWND";

AApplication::AApplication(void)
{
	pApp = this;
	m_fInitialized=false;	//True, if Start() was called
	m_fPause=false;
	m_hWindow=0;		//Need for work with window on WinAPI
	m_hGLRC=0;		//Need for work with OpenGL

	m_uiWidth=0;
	m_uiHeight=0;
	m_fFullScreen=false;

	
	//Saving user's display's settings
	if(0==EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &m_dmStartUp))
		LOG_ERROR("Can not get current display's parameters!");

	SetProcessAffinityMask(GetCurrentProcess(), 1);

#ifdef _DEBUG
	POINT	*pResolutions=0;
	unsigned int uiRes=GetDisplaySettings(pResolutions);

	char szOut[35];
	std::string	sOut;
	for(unsigned int ui=0; ui<uiRes; ui++)
	{
		memset(szOut, 0, sizeof(szOut));
		sprintf(szOut, "Resolution founded: %d:%d", pResolutions[ui].x, pResolutions[ui].y); 
		sOut+=szOut;
		sOut+="<br/>\n";
	}
	LOG_HINT(sOut.c_str());
	delete pResolutions;
#endif
}

AApplication::~AApplication(void)
{
	SavePreferences();
}

void AApplication::SetFullScreen(bool fMode)
{
	m_fFullScreen = fMode;

	if(!m_fInitialized)return;
	
	if(CheckFullScreen() != fMode)
	{
		if(fMode=false)
		{
			if(0==SetWindowLong(m_hWindow, GWL_STYLE, WS_POPUP))
				LOG_ERROR("Can't change style of window!");
			if(0==SetWindowLong(m_hWindow, GWL_EXSTYLE, WS_EX_TOPMOST))
				LOG_ERROR("Can't change style of window!");
		}else
		{
			if(0==SetWindowLong(m_hWindow, GWL_STYLE, WS_OVERLAPPEDWINDOW))
				LOG_ERROR("Can't change style of window!");
			if(0==SetWindowLong(m_hWindow, GWL_EXSTYLE, 0))
				LOG_ERROR("Can't change style of window!");
		}
		LOG_SUCCESS("Fullscreen mode has changed!");
	}
	
	ApplyDisplayPreferences();
}

bool AApplication::CheckFullScreen()const
{
	return m_fFullScreen;
}

void AApplication::SetSize(unsigned int uiWidth, unsigned int uiHeight)
{
	if(uiWidth==0)
		m_uiWidth = GetSystemMetrics(SM_CXSCREEN);
	else 
		m_uiWidth = uiWidth;

	if(uiHeight==0)
		m_uiHeight = GetSystemMetrics(SM_CYSCREEN);
	else
		m_uiHeight = uiHeight;

	if(!m_fInitialized)
		return;

	if(m_uiWidth!=(unsigned int)GetSystemMetrics(SM_CXSCREEN))
		SetFullScreen(false);
	if(m_uiHeight!=(unsigned int)GetSystemMetrics(SM_CYSCREEN))
		SetFullScreen(false);

	if(0==SetWindowPos(m_hWindow, 0, 0, 0, m_uiWidth, m_uiHeight, 0))
		LOG_ERROR("Can't change window's pos!");
		


}
void AApplication::GetSize(unsigned int &uiWidth, unsigned int &uiHeight)
{
	if(!m_fInitialized)
	{
		uiWidth = m_uiWidth;
		uiHeight = m_uiHeight;
		return;
	};

	//Updating private variables by actual data
	RECT wndPos;
	if(0==GetWindowRect(m_hWindow, &wndPos))
		LOG_ERROR("Can't get window's pos!");
	m_uiWidth = wndPos.right-wndPos.left;
	m_uiHeight = wndPos.bottom-wndPos.top;

	//And return it to user
	uiWidth = m_uiWidth;
	uiHeight = m_uiHeight;
	return;
}

void AApplication::Minimize()
{
	if(!m_fInitialized)return;
	if(m_fFullScreen)ReturnDisplayPreferences();

	m_fPause = true;

	LONG	CurrentState;	//Current window's state
	CurrentState=GetWindowLong(m_hWindow, GWL_STYLE);

	if(!(CurrentState & WS_MINIMIZE))
	{
		ShowWindow(m_hWindow, SW_SHOWMINIMIZED);
		if(0==SetWindowLong(m_hWindow, GWL_STYLE, CurrentState | WS_MINIMIZE))
			LOG_ERROR("Application can't be minimized!");
		else
			LOG_SUCCESS("Application has minimized!");
	}

}

void AApplication::Maximize()
{
	if(!m_fInitialized)return;
	
	SetFullScreen(true);
	ApplyDisplayPreferences();

	LONG	CurrentState;	//Current window's state
	CurrentState=GetWindowLong(m_hWindow, GWL_STYLE);

	if(!(CurrentState & WS_MAXIMIZE))
	{
		ShowWindow(m_hWindow,SW_SHOWMAXIMIZED);
		if(0==SetWindowLong(m_hWindow, GWL_STYLE, CurrentState | WS_MAXIMIZE))
			LOG_ERROR("Application can't be maximized");
		else
			LOG_SUCCESS("Application has maximized!");
	}
}
void AApplication::Restore()
{
	if(!m_fInitialized)return;

	SetFullScreen(m_fFullScreen);
	if(m_fFullScreen)
		ApplyDisplayPreferences();
	LONG	CurrentState;	//Current window's state
	CurrentState=GetWindowLong(m_hWindow, GWL_STYLE);

	if(!(CurrentState & (WS_MINIMIZE|WS_MAXIMIZE)))
	{
		if(0==SetWindowLong(m_hWindow, GWL_STYLE, CurrentState & (WS_MINIMIZE|WS_MAXIMIZE)))
			LOG_ERROR("Application can't be restored!");
		else
			LOG_SUCCESS("Application has restored!");
	}

	SetActiveWindow(m_hWindow);
}

bool AApplication::IsMax()const
{
	if(!m_fInitialized)
	{
		LOG_WARNING("You are trying to receive information about the window, when it is not exist.");
		throw std::runtime_error("You are trying to receive information about the window, when it is not exist.");
	}
	LONG CurrentState;
	CurrentState = GetWindowLong(m_hWindow, GWL_STYLE);
	return (CurrentState & WS_MAXIMIZE);

}
bool AApplication::IsMin()const
{
	if(!m_fInitialized)
	{
		LOG_WARNING("You are trying to receive information about the window, when it is not exist.");
		throw std::runtime_error("You are trying to receive information about the window, when it is not exist.");
	}
	LONG CurrentState;
	CurrentState = GetWindowLong(m_hWindow, GWL_STYLE);
	return (CurrentState & WS_MINIMIZE);
}

unsigned int AApplication::Start(const char* szCaption)
{
	if(m_fInitialized)
	{
		m_fPause=false;
		return 0;
	}
	MakeWindow(szCaption);
	InitOpenGL();

	m_fInitialized = true;
	ApplyDisplayPreferences();
	OnStart();

	DWORD dwLastDifference = 1;
	__int64 largeTime;
	__int64	i64Freq;
	__int64 largeLastTime;
	QueryPerformanceFrequency((LARGE_INTEGER*)&i64Freq);
	QueryPerformanceCounter((LARGE_INTEGER*)&largeTime);
	double freq = i64Freq;
	double diff;

	MSG msg;
	memset(&msg, 0, sizeof(MSG));

	while(msg.message!=WM_QUIT)
	{
		if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			//We are can't take to function 0 - may be errors.

			QueryPerformanceCounter((LARGE_INTEGER*)&largeLastTime);
			
			diff = largeLastTime - largeTime;
			
			dwLastDifference = 1000000.0*diff/freq;
			//if( dwLastDifference != 0)
			{
				//Updating stats...
				QueryPerformanceCounter((LARGE_INTEGER*)&largeTime);
				if(m_fPause)
					OnPause( dwLastDifference);
				else
				{
					//TimerSystem.UpdateAll(dwLastDifference);
					OnUpdate( dwLastDifference);
				}
				
			};

		}
	}
	
	OnStop();
	ReturnDisplayPreferences();
	m_fInitialized=false;
	DeInitOpenGL();
	DeInitWindow();
	return 0;
}

void AApplication::Stop()
{
	PostQuitMessage(0);
}

bool AApplication::SavePreferences()const
{
	std::ofstream	outPrefer("preferences.app", 
		std::ios::binary|std::ios::beg|std::ios::trunc);
	if(!outPrefer.is_open())
	{
		LOG_WARNING("Can not save preferences.");
		return false;
	}
	outPrefer.write((char*)&m_uiWidth, sizeof(m_uiWidth));
	outPrefer.write((char*)&m_uiHeight, sizeof(m_uiHeight));

	outPrefer.write((char*)&m_fFullScreen, 1);
	outPrefer.close();
}
bool AApplication::LoadPreferences()
{
	std::ifstream	inPrefer("preferences.app", 
		std::ios::binary|std::ios::beg|std::ios::trunc);
	if(!inPrefer.is_open())
	{
		LOG_HINT("Application's preferences is not found.");
		return false;
	}
	inPrefer.read((char*)&m_uiWidth, sizeof(m_uiWidth));
	inPrefer.read((char*)&m_uiHeight, sizeof(m_uiHeight));

	inPrefer.read((char*)&m_fFullScreen, 1);
	inPrefer.close();
	return true;
}

void AApplication::MakeWindow(const char *szCaption)
{
	WNDCLASSEX	wndClass;
	wndClass.cbClsExtra				=0;
	wndClass.cbSize					= sizeof(WNDCLASSEX);
	wndClass.cbWndExtra				=0;
	wndClass.hbrBackground			= (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.hCursor				= LoadCursor(0, IDC_ARROW);
	wndClass.hIcon=wndClass.hIconSm = LoadIcon(0, IDI_APPLICATION);
	wndClass.hInstance				= GetModuleHandle(NULL);
	wndClass.lpszClassName			= g_cClassName;
	wndClass.lpszMenuName			= 0;
	wndClass.style					= CS_VREDRAW|CS_HREDRAW|CS_OWNDC;
	wndClass.lpfnWndProc			= WndProc;

	if(!RegisterClassEx(&wndClass))
	{
		LOG_FATALERROR("Can't register the window's class!");
		throw std::runtime_error("Can't register window class!");
	}
	DWORD dwStyle=WS_OVERLAPPED|WS_MAXIMIZEBOX|WS_MINIMIZEBOX|WS_SYSMENU, dwExStyle=0;
	if(m_fFullScreen)
	{
		dwStyle = WS_POPUP;
#ifndef _DEBUG
		dwExStyle = WS_EX_TOPMOST;
#else	
		dwExStyle = 0;
#endif
	}

	SetSize(m_uiWidth, m_uiHeight);

	m_hWindow=CreateWindowEx(dwExStyle, g_cClassName, szCaption, dwStyle, 0, 0, m_uiWidth, m_uiHeight, 0, 0, GetModuleHandle(0), 0);
	if(m_hWindow==0)
	{
		LOG_FATALERROR("Can't create the window!");
		throw(std::runtime_error("Can't create the window!"));
	}

	ShowWindow(m_hWindow, SW_SHOW);
	UpdateWindow(m_hWindow);

	//g_Callbacks.insert(WNDCALLBACKPAIR(m_hWindow, this));	//Register our AApplication in WndProc, for process messages

}

void AApplication::DeInitWindow()
{
	//Delete our call-back proc from WNDPROC
	//WNDCALLBACK_ITER iter=g_Callbacks.find(m_hWindow);
	//g_Callbacks.erase(iter);

	DestroyWindow(m_hWindow);
	UnregisterClass(g_cClassName, GetModuleHandle(0));

}

void AApplication::InitOpenGL()
{
	//Work with pixel format description...
	HDC hTempDC = GetDC(m_hWindow);

	PIXELFORMATDESCRIPTOR	pfd;
	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);

	int nPixelFormat = GetPixelFormat(hTempDC);
	DescribePixelFormat(hTempDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
	pfd.dwFlags	= PFD_DOUBLEBUFFER|PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL;
	pfd.cColorBits = pfd.cDepthBits = 32;

	nPixelFormat = ChoosePixelFormat(hTempDC, &pfd);
	SetPixelFormat(hTempDC, nPixelFormat, &pfd);

	//Initializing opengl rendering context...
	m_hGLRC = wglCreateContext(hTempDC);
	wglMakeCurrent(hTempDC, m_hGLRC);
	glClearColor(0.5f, 0.5f, 0.5f, 0);
	glClear(GL_COLOR_BUFFER_BIT);

	glEnable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	GLfloat light_pos[]={0.0f, 2.0f, 0.0f, 1.0f};
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
	//glLightfv(GL_LIGHT0, GL_LIGHT)
	gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45, (double)m_uiWidth/(double)m_uiHeight, 0.01, 90);
	gluOrtho2D(-1, 1, -1, 1);

	glEnable(GL_LIGHTING);
	glEnable(GL_NORMALIZE);
	
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);

	GLfloat global_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);


	glEnable(GL_TEXTURE);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		GL_LINEAR);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

#ifdef _DEBUG
	glPolygonMode(GL_FRONT, GL_LINE);
#endif

	//glEnable(GL_FOG);
//	glFog()


	
	

}

void AApplication::ApplyDisplayPreferences()
{
	if(!m_fInitialized)
	{
		LOG_WARNING("You are trying change videomode, when window was not initialize!");
		return;
	}
	if(m_fFullScreen)
	{
		DEVMODE	devMode;
		memset(&devMode, 0, sizeof(DEVMODE));
		devMode.dmSize = sizeof(DEVMODE);
		devMode.dmFields = DM_PELSHEIGHT|DM_PELSWIDTH;
		
		devMode.dmPelsWidth	= m_uiWidth;
		devMode.dmPelsHeight= m_uiHeight;

		LONG	lResult=ChangeDisplaySettings(&devMode, CDS_FULLSCREEN);
#ifdef _DEBUG
		if(lResult==DISP_CHANGE_BADFLAGS)
		{
			LOG_FATALERROR("Invalid flags in function ChangeDisplaySettings!");
			throw std::runtime_error("Invalid flags in function ChangeDisplaySettings!");
		}

		if(lResult==DISP_CHANGE_BADPARAM)
		{
			LOG_FATALERROR("Invalid parameters in function ChangeDisplaySettings!");
			throw std::runtime_error("Invalid parameters in function ChangeDisplaySettings!");
		}
#endif
		if(lResult!=DISP_CHANGE_SUCCESSFUL)
		{ 
			LOG_ERROR("Can't change display settings!");
			return;
		}

		LOG_SUCCESS("Display mode has successfully changed.");
	}
}

void	AApplication::ReturnDisplayPreferences()
{
	if(!m_fInitialized)
	{
		LOG_WARNING("You are trying return videomode, when window was not initialize!");
		return;
	}
	
	
	if(DISP_CHANGE_SUCCESSFUL==ChangeDisplaySettings(0, 0))
		LOG_SUCCESS("Returning display settings again.");
	else
		LOG_ERROR("Can't return user's settings of display!");
}

void AApplication::DeInitOpenGL()
{
	wglMakeCurrent(0, 0);
	wglDeleteContext(m_hGLRC);
}
LRESULT CALLBACK AApplication::m_WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	static POINT	lastPointer={0,0};
	switch(uiMsg)
	{
	case WM_CLOSE:		PostQuitMessage(0);break;
	case WM_KEYDOWN:	OnKeydown((unsigned char)wParam); break;
	case WM_KEYUP:		OnKeyup((unsigned char)wParam); break;
	case WM_MOUSEMOVE:	
		OnMouseMoveAbsolute(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		OnMouseMoveRelative((long)GET_X_LPARAM(lParam)-lastPointer.x, (long)GET_Y_LPARAM(lParam)-lastPointer.y);
		lastPointer.x = (long)GET_X_LPARAM(lParam);
		lastPointer.y = (long)GET_Y_LPARAM(lParam);
		break;
	case WM_MOUSEWHEEL:	OnMouseWheel(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), GET_WHEEL_DELTA_WPARAM(wParam));break;
	case WM_MBUTTONDOWN:OnMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_MBUTTON);break;
	case WM_MBUTTONUP:	OnMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_MBUTTON);break;
	case WM_LBUTTONDOWN:OnMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_LBUTTON);break;
	case WM_LBUTTONUP:	OnMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_LBUTTON);break;
	case WM_RBUTTONDOWN:OnMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_RBUTTON);break;
	case WM_RBUTTONUP:	OnMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), MK_RBUTTON);break;
	case WM_KILLFOCUS:	OnKillFocus();break;
	case WM_SETFOCUS:	OnFocus();break;
	case WM_SIZE:		
		m_uiWidth = LOWORD(lParam);
		m_uiHeight = HIWORD(lParam);
		glMatrixMode(GL_PROJECTION);
		if(m_uiHeight!=0)
		{
			glLoadIdentity();
			glViewport(0, 0, m_uiWidth, m_uiHeight);
			float fAspectRatio=(float)m_uiWidth/(float)m_uiHeight;
			gluPerspective(90.0f, fAspectRatio, 0.001f, 10.0f);
			gluLookAt(0, 0, -1, 0, 0, 0, 0, 1, 0);
		}
		

		glMatrixMode(GL_MODELVIEW);
		break;
	}

	return DefWindowProc(hWnd, uiMsg, wParam, lParam);
}

int	AApplication::GetDisplaySettings(POINT	*&aPoints)const
{
	unsigned int	uiCurrentMode = 0;

	DEVMODE	dm;
	dm.dmSize			=sizeof(dm);
	dm.dmDriverExtra	=0;

	//Calculating count of available display's mode
	for(unsigned int ui=0; 0!=EnumDisplaySettings(0, ui, &dm);ui++, uiCurrentMode++);
	
	//Allocating memory
	aPoints = new POINT[uiCurrentMode];

	//Filling modes...
	for(unsigned int ui=0; ui<uiCurrentMode; ui++)
	{
		EnumDisplaySettings(0, ui, &dm);
		aPoints[ui].x = dm.dmPelsWidth;
		aPoints[ui].y = dm.dmPelsHeight;
	}

	//Removing duplicates...
	unsigned int uiCount = uiCurrentMode;
	for(unsigned int ui=0; ui<uiCurrentMode-1; ui++)
		if(aPoints[ui].x==aPoints[ui+1].x)
			if(aPoints[ui].y==aPoints[ui+1].y)
			{
				aPoints[ui].x = 0;
				aPoints[ui].y = 0;
				uiCount--;
			}

	POINT			*pResult=new POINT[uiCount];
	unsigned int	uiCurrentResult=0;
	for(unsigned int ui=0; ui<uiCurrentMode; ui++)
	{
		if(aPoints[ui].x!=0)
			if(aPoints[ui].y!=0)
			{
				pResult[uiCurrentResult].x = aPoints[ui].x;
				pResult[uiCurrentResult].y = aPoints[ui].y;
				uiCurrentResult++;
			}
	}
	
	delete aPoints;
	   
	aPoints = pResult;
	return uiCurrentResult;	
}

//LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
//{
//
//	WNDCALLBACK_ITER	iter = g_Callbacks.find(hWnd);
//	if(iter!=g_Callbacks.end())
//		return iter->second->m_WndProc(hWnd, uiMsg, wParam, lParam);
//
//	return DefWindowProc(hWnd, uiMsg, wParam, lParam);
//
//}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	return pApp->m_WndProc(hWnd, uiMsg, wParam, lParam);
}